diff --git a/back/database/busRoute.js b/back/database/busRoute.js new file mode 100644 index 0000000..4700246 --- /dev/null +++ b/back/database/busRoute.js @@ -0,0 +1,9 @@ +module.exports = { + "200000103": "9", + "234000016": "1112", + "200000115": "5100", + "200000112": "7000", + "234001243": "M5107", + "234000884": "1560A", + "228000433": "1560B" +}; \ No newline at end of file diff --git a/back/database/index.js b/back/database/index.js index 5036d3a..a411d30 100644 --- a/back/database/index.js +++ b/back/database/index.js @@ -8,285 +8,15 @@ const request = require('request'); const fs2 = require('fs').promises; const { DateTime } = require('luxon'); const session=[]; -const bus= { - lastUpdate: null, - currentData: null, - updateInterval: null, - routeId: null, - routeName: null, - stationId: null, - staOrder: null, - predictTime1: null, - predictTime2: null, - remainSeatCnt1: null, - remainSeatCnt2: null, - -}; -const busRouteMap = { - "200000103": "9", - "234000016": "1112", - "200000115": "5100", - "200000112": "7000", - "234001243": "M5107", - "234000884": "1560A", - "228000433": "1560B" - -}; -const stationMap = { - "228001174": "사색의광장(정문행)", - "228000704": "생명과학대.산업대학(정문행)", - "228000703": "경희대체육대학.외대(정문행)", - "203000125": "경희대학교(정문행)", - "228000723": "경희대정문(사색행)", - "228000710": "외국어대학(사색행)", - "228000709": "생명과학대(사색행)", - "228000708": "사색의광장(사색행)", - "228000706": "경희대차고지(1)", - "228000707": "경희대차고지(2)" - //"203000037": "경희대정문(사색행)" - - -}; const busArrival = { lastUpdate: null, currentData: {}, // stopId를 키로 사용 updateIntervals: {} // 각 정류장별 인터벌 저장 }; -const specialRouteMapping={ - "228000710": { // 외국어대학(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - } - }, - "228000709": { // 생명과학대(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2,// 도착 예정 시간에 더할 시간(분) - staOrderOffset: 2 //정류장 순서에 더할 값 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2, - staOrderOffset: 2 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2, - staOrderOffset: 2 - } - }, - "228000708": { // 사색의광장(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - } - - } -}; - -const specialRouteMappingFullPath = { - "228000710": { // 외국어대학(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 1, - staOrderOffset: 1 - } - }, - "228000709": { // 생명과학대(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2,// 도착 예정 시간에 더할 시간(분) - staOrderOffset: 2 //정류장 순서에 더할 값 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2, - staOrderOffset: 2 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 2, - staOrderOffset: 2 - } - }, - - "228000708": { // 사색의광장(사색행) - "234001243": { // M5107 - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - }, - "234000884": { // 1560A - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - }, - "228000433": { // 1560B - referenceStationId: "228000723", // 경희대정문(사색행) - timeOffset: 3, - staOrderOffset: 3 - } - - }, - "228001174": { // 사색의광장(정문행) - "234001243": { // M5107 - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -3, - staOrderOffset: -3 - }, - "234000884": { // 1560A - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -3, - staOrderOffset: -3 - }, - "228000433": { // 1560B - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -3, - staOrderOffset: -3 - } - }, - "228000704": { // 생명과학대.산업대학(정문행) - "234001243": { // M5107 - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -2, - staOrderOffset: -2 - }, - "234000884": { // 1560A - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -2, - staOrderOffset: -2 - }, - "228000433": { // 1560B - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -2, - staOrderOffset: -2 - } - }, - "228000703": { // 경희대체육대학.외대(정문행) - "234001243": { // M5107 - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -1, - staOrderOffset: -1 - }, - "234000884": { // 1560A - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -1, - staOrderOffset: -1 - }, - "228000433": { // 1560B - referenceStationId: "203000125", // 경희대학교(정문행) - timeOffset: -1, - staOrderOffset: -1 - } - } -}; -const stationRouteOrder = { - "228001174":{//사색의광장(정문행) - "200000103": "1",//9번 - "200000115": "1",//5100 - "200000112": "1",//7000 - "234001243": "1",//M5107 - "234000884": "1",//1560A - "234000016": "1",//1112 - "228000433": "1",//1560B - }, - "228000704":{//생명과학대.산업대학(정문행) - "200000103": "2",//9번 - "200000115": "2",//5100 - "200000112": "2",//7000 - "234001243": "2",//M5107 - "234000884": "2",//1560A - "234000016": "2",//1112 - "228000433": "2",//1560B - }, - "228000703":{//경희대체육대학.외대(정문행) - "200000103": "3",//9번 - "200000115": "3",//5100 - "200000112": "3",//7000 - "234001243": "3",//M5107 - "234000884": "3",//1560A - "234000016": "3",//1112 - "228000433": "3",//1560B - }, - "203000125":{//경희대학교(정문행) - "200000103": "4",//9번 - "200000115": "4",//5100 - "200000112": "4",//7000 - "234001243": "4",//M5107 - "234000884": "4",//1560A - "234000016": "4",//1112 - "228000433": "4",//1560B - }, - "228000723":{//경희대정문(사색행) - "200000103": "87",//9번 - "200000115": "56",//5100 - "200000112": "76",//7000 - "234001243": "60",//M5107 - "234000884": "99",//1560A - "234000016": "68",//1112 - "228000433": "99",//1560B - }, - "228000710":{//외국어대학(사색행) - "200000103": "88",//9번 - "200000115": "57",//5100 - "200000112": "77",//7000 - "234001243": "61",//M5107 - "234000884": "100",//1560A - "234000016": "69",//1112 - "228000433": "100",//1560B - }, - "228000709":{//생명과학대(사색행) - "200000103": "89",//9번 - "200000115": "58",//5100 - "200000112": "78",//7000 - "234001243": "62",//M5107 - "234000884": "101",//1560A - "234000016": "70",//1112 - "228000433": "101",//1560B - }, - "228000708":{//사색의광장(사색행) - "200000103": "90",//9번 - "200000115": "59",//5100 - "200000112": "79",//7000 - "234001243": "63",//M5107 - "234000884": "102",//1560A - "234000016": "71",//1112 - "228000433": "102",//1560B - } -}; +const busRouteMap = require('./busRoute'); +const stationMap = require('./station'); +const { specialRouteMapping: specialRouteMapping, specialRouteMappingFullPath: specialRouteMappingFullPath } = require('./specialRoute'); +const stationRouteOrder = require('./stationRouteOrder'); const busStationData = JSON.parse( fs.readFileSync(path.join(__dirname, '버스정류소현황.json'), 'utf8') ); @@ -317,12 +47,6 @@ const setSession2 = function(id, cookie) { const getSession = function(id) { return session.find(user => user.id == id)['Cookie']; } -const getSession2 = function(id) { - return session.find(user => user.id == id)['Cookie2']; -} -const getSession3= function(id) { - return session.find(user => user.id == id)['reserveReserve']; -} process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; @@ -338,7 +62,6 @@ function getpublickey(callback) { } - function login(id, pw, callback, ecallback) { getpublickey((key, cookie) => { let enc = new JSEncrypt() @@ -822,48 +545,7 @@ function getStoredPredictionsByStation(stationId) { } return predictions; } -//past history -/*function getPastBusArrival(routeId, stationId, staOrder, date) { - return new Promise((resolve, reject) => { - const url = 'https://api.gbis.go.kr/ws/rest/pastarrivalservice/json'; - const queryParams = new URLSearchParams({ - serviceKey: gbIsApiKey, // .env 파일에 GBIS API 키 추가 필요 - sDay: date, // YYYY-MM-DD 형식 - routeId: routeId, - stationId: stationId, - staOrder: staOrder - }).toString(); - - request({ - url: `${url}?${queryParams}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'User-Agent': 'Mozilla/5.0', - 'Origin': 'https://m.gbis.go.kr', - 'Referer': 'https://m.gbis.go.kr/' - } - }, function(error, response, body) { - if (error) { - console.error('과거 버스 도착 정보 조회 실패:', error); - reject(error); - return; - } - try { - const data = JSON.parse(body); - resolve({ - ok: true, - data: data, - lastUpdate: new Date() - }); - } catch (e) { - console.error('데이터 처리 오류:', e); - reject(e); - } - }); - }); -}*/ function getStaOrder(stationId, routeId) { return stationRouteOrder[stationId]?.[routeId]; } @@ -1029,5 +711,5 @@ async function fetchBusHistory(routeId, stationId, staOrder, date) { module.exports = { - login,getMID, setSession, getSession, setSession2, getSession2, getUserInfo, getSession3, mybusinfo, getUserSession, getBusArrival, getStoredPredictions, startMonitoring, stopMonitoring, getStoredPredictionsByStation, getPastBusArrival, getStaOrder, stationRouteOrder,busRouteMap,stationMap,logout + login,getMID, setSession, getSession, setSession2, getUserInfo,mybusinfo, getUserSession, getBusArrival, getStoredPredictions, startMonitoring, stopMonitoring, getStoredPredictionsByStation, getPastBusArrival, getStaOrder, stationRouteOrder,busRouteMap,stationMap,logout }; diff --git a/back/database/specialRoute.js b/back/database/specialRoute.js new file mode 100644 index 0000000..f0a1355 --- /dev/null +++ b/back/database/specialRoute.js @@ -0,0 +1,161 @@ +module.exports={specialRouteMapping: { + "228000710": { // 외국어대학(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + } + }, + "228000709": { // 생명과학대(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2,// 도착 예정 시간에 더할 시간(분) + staOrderOffset: 2 //정류장 순서에 더할 값 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2, + staOrderOffset: 2 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2, + staOrderOffset: 2 + } + }, + "228000708": { // 사색의광장(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + } + + } +}, +specialRouteMappingFullPath: { + "228000710": { // 외국어대학(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 1, + staOrderOffset: 1 + } + }, + "228000709": { // 생명과학대(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2,// 도착 예정 시간에 더할 시간(분) + staOrderOffset: 2 //정류장 순서에 더할 값 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2, + staOrderOffset: 2 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 2, + staOrderOffset: 2 + } + }, + + "228000708": { // 사색의광장(사색행) + "234001243": { // M5107 + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + }, + "234000884": { // 1560A + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + }, + "228000433": { // 1560B + referenceStationId: "228000723", // 경희대정문(사색행) + timeOffset: 3, + staOrderOffset: 3 + } + + }, + "228001174": { // 사색의광장(정문행) + "234001243": { // M5107 + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -3, + staOrderOffset: -3 + }, + "234000884": { // 1560A + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -3, + staOrderOffset: -3 + }, + "228000433": { // 1560B + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -3, + staOrderOffset: -3 + } + }, + "228000704": { // 생명과학대.산업대학(정문행) + "234001243": { // M5107 + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -2, + staOrderOffset: -2 + }, + "234000884": { // 1560A + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -2, + staOrderOffset: -2 + }, + "228000433": { // 1560B + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -2, + staOrderOffset: -2 + } + }, + "228000703": { // 경희대체육대학.외대(정문행) + "234001243": { // M5107 + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -1, + staOrderOffset: -1 + }, + "234000884": { // 1560A + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -1, + staOrderOffset: -1 + }, + "228000433": { // 1560B + referenceStationId: "203000125", // 경희대학교(정문행) + timeOffset: -1, + staOrderOffset: -1 + } + } +} +}; \ No newline at end of file diff --git a/back/database/station.js b/back/database/station.js new file mode 100644 index 0000000..5ebea6d --- /dev/null +++ b/back/database/station.js @@ -0,0 +1,12 @@ +module.exports = { + "228001174": "사색의광장(정문행)", + "228000704": "생명과학대.산업대학(정문행)", + "228000703": "경희대체육대학.외대(정문행)", + "203000125": "경희대학교(정문행)", + "228000723": "경희대정문(사색행)", + "228000710": "외국어대학(사색행)", + "228000709": "생명과학대(사색행)", + "228000708": "사색의광장(사색행)", + "228000706": "경희대차고지(1)", + "228000707": "경희대차고지(2)" +}; \ No newline at end of file diff --git a/back/database/stationRouteOrder.js b/back/database/stationRouteOrder.js new file mode 100644 index 0000000..d26cf3b --- /dev/null +++ b/back/database/stationRouteOrder.js @@ -0,0 +1,74 @@ +module.exports = { + "228001174":{//사색의광장(정문행) + "200000103": "1",//9번 + "200000115": "1",//5100 + "200000112": "1",//7000 + "234001243": "1",//M5107 + "234000884": "1",//1560A + "234000016": "1",//1112 + "228000433": "1",//1560B + }, + "228000704":{//생명과학대.산업대학(정문행) + "200000103": "2",//9번 + "200000115": "2",//5100 + "200000112": "2",//7000 + "234001243": "2",//M5107 + "234000884": "2",//1560A + "234000016": "2",//1112 + "228000433": "2",//1560B + }, + "228000703":{//경희대체육대학.외대(정문행) + "200000103": "3",//9번 + "200000115": "3",//5100 + "200000112": "3",//7000 + "234001243": "3",//M5107 + "234000884": "3",//1560A + "234000016": "3",//1112 + "228000433": "3",//1560B + }, + "203000125":{//경희대학교(정문행) + "200000103": "4",//9번 + "200000115": "4",//5100 + "200000112": "4",//7000 + "234001243": "4",//M5107 + "234000884": "4",//1560A + "234000016": "4",//1112 + "228000433": "4",//1560B + }, + "228000723":{//경희대정문(사색행) + "200000103": "87",//9번 + "200000115": "56",//5100 + "200000112": "76",//7000 + "234001243": "60",//M5107 + "234000884": "99",//1560A + "234000016": "68",//1112 + "228000433": "99",//1560B + }, + "228000710":{//외국어대학(사색행) + "200000103": "88",//9번 + "200000115": "57",//5100 + "200000112": "77",//7000 + "234001243": "61",//M5107 + "234000884": "100",//1560A + "234000016": "69",//1112 + "228000433": "100",//1560B + }, + "228000709":{//생명과학대(사색행) + "200000103": "89",//9번 + "200000115": "58",//5100 + "200000112": "78",//7000 + "234001243": "62",//M5107 + "234000884": "101",//1560A + "234000016": "70",//1112 + "228000433": "101",//1560B + }, + "228000708":{//사색의광장(사색행) + "200000103": "90",//9번 + "200000115": "59",//5100 + "200000112": "79",//7000 + "234001243": "63",//M5107 + "234000884": "102",//1560A + "234000016": "71",//1112 + "228000433": "102",//1560B + } +}; \ No newline at end of file diff --git a/back/index.js b/back/index.js index 1def69f..538aea9 100644 --- a/back/index.js +++ b/back/index.js @@ -77,6 +77,7 @@ app.post('/user/login', (req, res) => { }) + app.post('/user/logout', (req, res) => { const id = req.body.id const cookie = req.body.cookie @@ -124,6 +125,8 @@ app.post('/user/logout', (req, res) => { }); }); }); + + app.get('/user/status', (req, res) => { const id = req.query.id; // id는 쿼리로 받고 const cookie = req.headers.authorization; // 쿠키는 헤더로 받음 @@ -167,8 +170,6 @@ app.get('/user/status', (req, res) => { }); - - app.get('/bus/:routeId/eta', (req, res) => { const routeId = req.params.routeId || '233000031'; // busId -> routeId로 수정 console.log('버스 도착 정보 요청:', { routeId: req.params.routeId }); @@ -216,6 +217,7 @@ app.get('/stop/:stationId/eta', (req, res) => { ); }); + app.get('/complain/:stationId/passedby', (req, res) => { const stationId = req.params.stationId; const predictions = database.getStoredPredictionsByStation(stationId); @@ -236,6 +238,7 @@ app.get('/complain/:stationId/passedby', (req, res) => { }); }); + app.get('/bus/history/byBus', async (req, res) => { try { const { routeId, stationId, date } = req.query; @@ -286,6 +289,7 @@ app.get('/bus/history/byBus', async (req, res) => { } }); + app.get('/bus/history/byTime', async (req, res) => { try { const { stationId, date } = req.query; diff --git a/front/lib/bus_history_page.dart b/front/lib/bus_history_page.dart index 8092ae3..7f49a95 100644 --- a/front/lib/bus_history_page.dart +++ b/front/lib/bus_history_page.dart @@ -220,6 +220,7 @@ class _BusHistoryPageState extends State { separatorBuilder: (context, index) => const Divider(height: 1), itemBuilder: (context, index) { return ListTile( + horizontalTitleGap: 8, leading: Icon(Icons.directions_bus, color: getBusColor(widget.busNumber)), title: Text( entry.value[index], diff --git a/front/lib/login_screen.dart b/front/lib/login_screen.dart new file mode 100644 index 0000000..1d4158d --- /dev/null +++ b/front/lib/login_screen.dart @@ -0,0 +1,159 @@ +// 로그인 화면 +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'navigation_screen.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final TextEditingController _idController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + + + Future _login() async { + final String id = _idController.text.trim(); + final String password = _passwordController.text.trim(); + if (id.isEmpty || password.isEmpty) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('아이디와 비밀번호를 입력해주세요.')), + ); + } + return; + } + + try { + final Uri url = Uri.parse('http://10.0.2.2:8081/user/login'); + //final Uri url = Uri.parse('http://localhost:8081/user/login');// 로그인 API 주소 + final response = await http.post( + url, + headers: {'Content-Type': 'application/json'}, + body: json.encode({'id': id, 'pw': password}), + ); + + if (response.statusCode == 200) { + final responseData = json.decode(response.body); + if (responseData['ok'] == true) { + final String userName = responseData['name']; + final String userId = responseData['id']; + final List cookie = responseData['cookie']; + if (mounted) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NavigationBarScreen( + userName: userName, + userId: userId, + cookie: cookie, + ), + ), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('로그인 실패')), + ); + } + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('로그인 실패: ${response.body}')), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('오류 발생: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 50), // 추가 여백 + const Text( + '로그인', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + const Text( + 'ⓘ 인포21 아이디와 비밀번호로 로그인해주세요', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + const SizedBox(height: 30), + TextField( + controller: _idController, + decoration: InputDecoration( + labelText: '아이디', + hintText: 'exampleID', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: '비밀번호', + hintText: '************', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + const SizedBox(height: 30), + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: _login, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + child: const Text( + '로그인', + style: TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/front/lib/main.dart b/front/lib/main.dart index 6c2f275..41b5f93 100644 --- a/front/lib/main.dart +++ b/front/lib/main.dart @@ -1,11 +1,6 @@ //메인 페이지 import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; -import "bus_screen.dart"; -import "station_screen.dart"; -import "map_screen.dart"; -import "compliant_service_screen.dart"; +import "login_screen.dart"; void main() { runApp(const MyApp()); } @@ -26,324 +21,3 @@ class MyApp extends StatelessWidget { } } -class LoginScreen extends StatefulWidget { - const LoginScreen({super.key}); - - @override - State createState() => _LoginScreenState(); -} - -class _LoginScreenState extends State { - final TextEditingController _idController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); - - - Future _login() async { - final String id = _idController.text.trim(); - final String password = _passwordController.text.trim(); - if (id.isEmpty || password.isEmpty) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('아이디와 비밀번호를 입력해주세요.')), - ); - } - return; - } - - try { - final Uri url = Uri.parse('http://10.0.2.2:8081/user/login'); - //final Uri url = Uri.parse('http://localhost:8081/user/login');// 로그인 API 주소 - final response = await http.post( - url, - headers: {'Content-Type': 'application/json'}, - body: json.encode({'id': id, 'pw': password}), - ); - - if (response.statusCode == 200) { - final responseData = json.decode(response.body); - if (responseData['ok'] == true) { - final String userName = responseData['name']; - final String userId = responseData['id']; - final List cookie = responseData['cookie']; - if (mounted) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => NavigationBarScreen( - userName: userName, - userId: userId, - cookie: cookie, - ), - ), - ); - } - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('로그인 실패')), - ); - } - } - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('로그인 실패: ${response.body}')), - ); - } - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('오류 발생: $e')), - ); - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 50), // 추가 여백 - const Text( - '로그인', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - const Text( - 'ⓘ 인포21 아이디와 비밀번호로 로그인해주세요', - style: TextStyle( - fontSize: 14, - color: Colors.grey, - ), - ), - const SizedBox(height: 30), - TextField( - controller: _idController, - decoration: InputDecoration( - labelText: '아이디', - hintText: 'exampleID', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - labelText: '비밀번호', - hintText: '************', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - ), - const SizedBox(height: 30), - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: _login, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - child: const Text( - '로그인', - style: TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} - -class NavigationBarScreen extends StatefulWidget { - final String userName; - final String userId; - final List cookie; - - const NavigationBarScreen({ - super.key, - required this.userName, - required this.userId, - required this.cookie, - }); - - @override - State createState() => _NavigationBarScreenState(); -} - -class _NavigationBarScreenState extends State { - int currentPageIndex = 4; // "내 정보" 화면이 기본 활성화 상태 - - late final List _pages; - - @override - void initState() { - super.initState(); - _pages = [ - const ComplaintServiceScreen(), // 민원 화면 - const BusScreen(), // 버스 화면 - const MapScreen(), // 지도 화면 - const StationScreen(stationName: "정류장"), // 정류장 화면 - UserInfoScreen( - userName: widget.userName, - userId: widget.userId, - cookie: widget.cookie, - ), // 내 정보 화면 - ]; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: _pages[currentPageIndex], // 현재 활성화된 화면 - bottomNavigationBar: NavigationBar( - selectedIndex: currentPageIndex, - onDestinationSelected: (int index) { - setState(() { - currentPageIndex = index; - }); - }, - destinations: const [ - NavigationDestination( - icon: Icon(Icons.warning_amber_rounded), - label: '민원', - ), - NavigationDestination( - icon: Icon(Icons.directions_bus), - label: '버스', - ), - NavigationDestination( - icon: Icon(Icons.map), - label: '지도', - ), - NavigationDestination( - icon: Icon(Icons.stop_circle_outlined), - label: '정류장', - ), - NavigationDestination( - icon: Icon(Icons.person), - label: '내 정보', - ), - ], - ), - ); - } -} - - - -class UserInfoScreen extends StatelessWidget { - final String userName; - final String userId; - final List cookie; - - const UserInfoScreen({ - super.key, - required this.userName, - required this.userId, - required this.cookie, - }); - - Future _logout(BuildContext context) async { - try { - final Uri url = Uri.parse('http://10.0.2.2:8081/user/logout'); // 로그아웃 API 주소 - //final Uri url = Uri.parse('http://localhost:8081/user/logout'); - final response = await http.post( - url, - headers: { - 'Content-Type': 'application/json', - }, - body: json.encode({'id': userId, - 'cookie': cookie.join('; ')}), - ); - - if (response.statusCode == 200) { - final responseData = json.decode(response.body); - if (responseData['ok'] == true) { - if (context.mounted) { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => const LoginScreen()), - (route) => false, - ); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('로그아웃 실패')), - ); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('로그아웃 실패: ${response.body}')), - ); - } - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('오류 발생: $e')), - ); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('내 정보'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.account_circle, size: 100, color: Colors.black54), - const SizedBox(height: 20), - Text( - userName, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - '학번: $userId', - style: const TextStyle(fontSize: 16), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: () => _logout(context), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - ), - child: const Text( - '로그아웃', - style: TextStyle(color: Colors.white), - ), - ), - ], - ), - ), - ); - } -} diff --git a/front/lib/navigation_screen.dart b/front/lib/navigation_screen.dart new file mode 100644 index 0000000..1e030cf --- /dev/null +++ b/front/lib/navigation_screen.dart @@ -0,0 +1,83 @@ +// 네비게이션 바 +import 'package:flutter/material.dart'; +import 'bus_screen.dart'; +import 'station_screen.dart'; +import 'map_screen.dart'; +import 'compliant_service_screen.dart'; +import 'user_info_screen.dart'; + + +class NavigationBarScreen extends StatefulWidget { + final String userName; + final String userId; + final List cookie; + + const NavigationBarScreen({ + super.key, + required this.userName, + required this.userId, + required this.cookie, + }); + + @override + State createState() => _NavigationBarScreenState(); +} + +class _NavigationBarScreenState extends State { + int currentPageIndex = 4; // "내 정보" 화면이 기본 활성화 상태 + + late final List _pages; + + @override + void initState() { + super.initState(); + _pages = [ + const ComplaintServiceScreen(), // 민원 화면 + const BusScreen(), // 버스 화면 + const MapScreen(), // 지도 화면 + const StationScreen(stationName: "정류장"), // 정류장 화면 + UserInfoScreen( + userName: widget.userName, + userId: widget.userId, + cookie: widget.cookie, + ), // 내 정보 화면 + ]; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _pages[currentPageIndex], // 현재 활성화된 화면 + bottomNavigationBar: NavigationBar( + selectedIndex: currentPageIndex, + onDestinationSelected: (int index) { + setState(() { + currentPageIndex = index; + }); + }, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.warning_amber_rounded), + label: '민원', + ), + NavigationDestination( + icon: Icon(Icons.directions_bus), + label: '버스', + ), + NavigationDestination( + icon: Icon(Icons.map), + label: '지도', + ), + NavigationDestination( + icon: Icon(Icons.stop_circle_outlined), + label: '정류장', + ), + NavigationDestination( + icon: Icon(Icons.person), + label: '내 정보', + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/front/lib/station_bus_list_page.dart b/front/lib/station_bus_list_page.dart index a3009ec..65e7b17 100644 --- a/front/lib/station_bus_list_page.dart +++ b/front/lib/station_bus_list_page.dart @@ -115,7 +115,7 @@ class _StationBusListPageState extends State { if (_scrollController.hasClients) { _scrollController.animateTo( closestIndex * 56.0, // ListTile의 대략적인 높이 - duration: const Duration(milliseconds: 300), + duration: const Duration(milliseconds: 1500), curve: Curves.easeOut, ); } diff --git a/front/lib/user_info_screen.dart b/front/lib/user_info_screen.dart new file mode 100644 index 0000000..0968fbf --- /dev/null +++ b/front/lib/user_info_screen.dart @@ -0,0 +1,96 @@ +// 내 정보 화면 +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'login_screen.dart'; + +class UserInfoScreen extends StatelessWidget { + final String userName; + final String userId; + final List cookie; + + const UserInfoScreen({ + super.key, + required this.userName, + required this.userId, + required this.cookie, + }); + + Future _logout(BuildContext context) async { + try { + final Uri url = Uri.parse('http://10.0.2.2:8081/user/logout'); // 로그아웃 API 주소 + //final Uri url = Uri.parse('http://localhost:8081/user/logout'); + final response = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode({'id': userId, + 'cookie': cookie.join('; ')}), + ); + + if (response.statusCode == 200) { + final responseData = json.decode(response.body); + if (responseData['ok'] == true) { + if (context.mounted) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => const LoginScreen()), + (route) => false, + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('로그아웃 실패')), + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('로그아웃 실패: ${response.body}')), + ); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('오류 발생: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('내 정보'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.account_circle, size: 100, color: Colors.black54), + const SizedBox(height: 20), + Text( + userName, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Text( + '학번: $userId', + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: () => _logout(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + child: const Text( + '로그아웃', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ), + ); + } +}